package org.KeyCrafter.server;

import org.KeyCrafter.ds.dict.Dict;
import org.KeyCrafter.server.aof.AofAttribute;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import static org.KeyCrafter.ds.dict.Dict.DICT_HT_INITIAL_SIZE;
import static org.KeyCrafter.server.AppendOnlyFile.backgroundRewriteDoneHandler;
import static org.KeyCrafter.util.TimeUtil.getLRUClock;
import static org.KeyCrafter.util.TimeUtil.microTime;

// 服务器周期时间函数（所有时间周期）
public class ServerCron implements Runnable{
    private static final int KeyCrafter_DBCRON_DBS_PER_CALL = 16;
    private static final int ACTIVE_EXPIRE_CYCLE_SLOW = 0;
    private static final int ACTIVE_EXPIRE_CYCLE_FAST = 1;
    private static final int ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP = 20;
    private static final int ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC = 25;
    private static final int ACTIVE_EXPIRE_CYCLE_FAST_DURATION = 1000;
    private static final int KeyCrafter_HT_MINFILL = 10; // 最小填满百分比
    KeyCrafterServer server;

    ServerCron(KeyCrafterServer server) {
        this.server = server;
    }

    @Override
    public void run() {
        // 更新时间缓存
        updateCachedTime();
        server.lruClock = getLRUClock();
        // 检查客户端，关闭超时客户端，并释放客户端多余的缓冲区
        clientsCron();
        // 对数据库执行各种操作
        databasesCron();
        // 检查 aof 重写情况
        aofCorn();
        // 增加 loop 计数器
        server.cronLoops++;
    }

    // 对数据库执行删除过期键，调整大小，以及主动和渐进式 rehash
    private int resizeDb = 0; // 要进行 resize 的数据库id（全局变量）
    private int rehashDb = 0; // 要进行 rehash 的数据库id（全局变量）

    private void databasesCron() {
        // 函数先从数据库中删除过期键，然后再对数据库的大小进行修改
        // 清除模式为 CYCLE_SLOW ，这个模式会尽量多清除过期键
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
        // 在没有 BGSAVE 或者 BGREWRITEAOF 执行时，对哈希表进行 rehash
        // if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
        // 这里先一定执行
        boolean ok = true;
        if (ok) {
            int dbsPerCall = KeyCrafter_DBCRON_DBS_PER_CALL;
            // 设定要测试的数据库数量
            if (dbsPerCall > server.dbNum) {
                dbsPerCall = server.dbNum;
            }
            // 调整字典的大小
            for (int j = 0; j < dbsPerCall; j++) {
                tryResizeHashTables(resizeDb % server.dbNum);
                resizeDb++;
            }
            // 对字典进行渐进式 rehash
            if (server.activeRehashing) {
                for (int j = 0; j < dbsPerCall; j++) {
                    boolean workDone = incrementallyRehash(rehashDb % server.dbNum);
                    rehashDb++;
                    if (workDone) {
                        // 如果函数做了一些工作，就在这里停止，将在下一个 cron 循环中做更多工作
                        break;
                    }
                }
            }
        }
    }


    int currentDb = 0; // 当前处理的数据库
    boolean timeLimitExit = false; // 是否退出过期键处理

    private void activeExpireCycle(int type) {
        int iteration = 0; // 遍历次数
        long timeLimit; // 执行时间限制
        // 函数开始的时间
        long start = microTime();

        // 函数处理的微秒时间上限
        // ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 默认为 25 ，也即是 25 % 的 CPU 时间
        // 这里默认计算出来是 25000 微妙
        timeLimit = 1000000 * ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC / server.hz / 100;
        if (timeLimit < 0) {
            timeLimit = 1;
        }

        // 如果是运行在快速模式之下
        // 那么最多只能运行 FAST_DURATION 微秒
        // 默认值为 1000 （微秒）
        if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
            timeLimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION;
        }

        // 默认每次处理的数据库数量
        int dbsPerCall = KeyCrafter_DBCRON_DBS_PER_CALL;
        if (dbsPerCall > server.dbNum || timeLimitExit) {
            dbsPerCall = server.dbNum;
        }
        // 遍历数据库
        for (int j = 0; j < dbsPerCall; j++) {
            int expired;
            // 指向要处理的数据库
            KVDB db = server.db[currentDb % server.dbNum];
            // 为 DB 计数器加一，如果进入 do 循环之后因为超时而跳出
            // 那么下次会直接从下个 DB 开始处理
            currentDb++;
            do {
                long num, slots;
                long now, ttlSum;
                int ttlSamples;
                // 获取数据库中带过期时间的键的数量
                // 如果该数量为 0 ，直接跳过这个数据库
                if ((num = db.getExpires().size()) == 0) {
                    db.setAvgTTL(0);
                    break;
                }
                // 获取数据库中过期键字典槽的数量
                slots = db.getExpires().slots();
                // 当前时间
                now = System.currentTimeMillis();
                // 这个数据库的使用率低于 1% ，扫描起来太费力了（大部分都会 MISS）
                // 跳过，等待字典收缩程序运行 (num/slots < 0.01)
                if (num > 0 && slots > DICT_HT_INITIAL_SIZE &&
                        (num * 100 / slots < 1)) {
                    break;
                }
                // 样本计数器
                // 已处理过期键计数器
                expired = 0;
                // 键的总 TTL 计数器
                ttlSum = 0;
                // 总共处理的键计数器
                ttlSamples = 0;
                // 每次最多只能检查 LOOKUPS_PER_LOOP 个键
                if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) {
                    num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
                }
                // 开始遍历数据库
                while (num-- > 0) {
                    Dict.Entry de;
                    long ttl;
                    // 从 expires 中随机取出一个带过期时间的键
                    if ((de = db.getExpires().getRandomKey()) == null) {
                        break;
                    }
                    // 计算 TTL
                    ttl = (long) de.getValue() - now;
                    // 如果键已经过期，那么删除它，并将 expired 计数器增一
                    if (activeExpireCycleTryExpire(db, de, now)) {
                        expired++;
                    }
                    if (ttl < 0) {
                        // 过期了，生存时间重置
                        ttl = 0;
                    }
                    // 累积键的 TTL
                    ttlSum += ttl;
                    // 累积处理键的个数
                    ttlSamples++;
                }
                // 为这个数据库更新平均 TTL 统计数据
                if (ttlSamples > 0) {
                    // 计算当前平均值
                    long avgTtl = ttlSum / ttlSamples;
                    // 如果这是第一次设置数据库平均 TTL ，那么进行初始化
                    if (db.getAvgTTL() == 0) {
                        db.setAvgTTL(avgTtl);
                    }
                    // 取数据库的上次平均 TTL 和今次平均 TTL 的平均值
                    db.setAvgTTL((db.getAvgTTL() + avgTtl) / 2);
                }
                // 不能用太长时间处理过期键，
                // 所以这个函数执行一定时间之后就要返回
                // 更新遍历次数
                iteration++;
                // 每遍历 16 次执行一次
                if ((iteration & 0xF) == 0 && (microTime() - start) > timeLimit) {
                    // 如果遍历次数正好是 16 的倍数
                    // 并且遍历的时间超过了 timeLimit
                    // 那么断开 timeLimitExit
                    timeLimitExit = true;
                }
                // 已经超时了，返回
                if (timeLimitExit) {
                    return;
                }
                // 如果已删除的过期键占当前总数据库带过期时间的键数量的 25 %
                // 那么不再遍历
            } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP / 4);
        }
    }

    /*
     * activeExpireCycle() 函数使用的检查键是否过期的辅佐函数。
     *
     * 如果 de 中的键已经过期，那么移除它，并返回 true ，否则不做动作，并返回 flase 。
     *
     * 参数 now 是毫秒格式的当前时间
     */
    private boolean activeExpireCycleTryExpire(KVDB db, Dict.Entry de, long now) {
        // 获取键的过期时间
        long t = (long) de.getValue();
        if (now > t) {
            // 键已经过期
            String key = (String) de.getKey();
            // 传播过期命令
            //propagateExpire(db,keyobj);
            // 从数据库中删除该键
            db.delete(key);
            // 发送事件
//                notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
//                        "expired",keyobj,db->id);
//                decrRefCount(keyobj);
            // 更新计数器
            server.statistics.incrExpiredKeys();
            return true;
        } else {
            // 键未过期
            return false;
        }
    }

    // 如果字典的使用率比 REDIS_HT_MINFILL 常量要低
    // 那么通过缩小字典的体积来节约内存
    private void tryResizeHashTables(int dbId) {
        if (htNeedsResize(server.db[dbId].getKeySpace())) {
            server.db[dbId].getKeySpace().resize();
        }
        if (htNeedsResize(server.db[dbId].getExpires())) {
            server.db[dbId].getExpires().resize();
        }
    }

    private boolean htNeedsResize(Dict dict) {
        long size, used;
        size = dict.slots();
        used = dict.size();
        return (size > 0 && used > 0 && size > DICT_HT_INITIAL_SIZE &&
                (used * 100 / size < KeyCrafter_HT_MINFILL));
    }

    /*
     * 虽然服务器在对数据库执行读取/写入命令时会对数据库进行渐进式 rehash ，
     * 但如果服务器长期没有执行命令的话，数据库字典的 rehash 就可能一直没办法完成，
     * 为了防止出现这种情况，我们需要对数据库执行主动 rehash 。
     *
     * 函数在执行了主动 rehash 时返回 true ，否则返回 false 。
     */
    private boolean incrementallyRehash(int dbId) {
        // 键空间
        if (server.db[dbId].getKeySpace().isRehashing()) {
            server.db[dbId].getKeySpace().rehashMilliseconds(1);
            return true;
        }
        // 过期键
        if (server.db[dbId].getExpires().isRehashing()) {
            server.db[dbId].getExpires().rehashMilliseconds(1);
            return true;
        }
        return false;
    }

    private void clientsCron() {

    }

    private void updateCachedTime() {
        server.mstime = System.currentTimeMillis();
    }

    private void aofCorn() {
        //检查 BGREWRITEAOF 是否已经执行完毕
        AofAttribute aof = server.aofAttribute;
        if (aof.getRewriteThreadId() != -1) {
            Future<Integer> rewriteFuture = aof.getRewriteFuture();
            if (rewriteFuture.isDone()) {
                // BGREWRITEAOF 执行完毕
                try {
                    backgroundRewriteDoneHandler(rewriteFuture.get());
                } catch (InterruptedException | ExecutionException e) {
                    throw new RuntimeException(e);
                }
            } else {
                // 遍历所有保存条件，看是否需要执行 BGREWRITEAOF 命令
                // do something...
            }
        }
    }
}
